/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/

/*  SimpleSockets - a simple Linux/Windows library for using TCP & UDP sockets.
 *  2007 (c) Renne Nissinen <rennex@iki.fi>
 */


#include "simplesockets.h"


/* Initializes the TCP/IP stack (needed on Windows).

   Returns 1 on success, 0 on failure (when unable to open the
   winsock dll).
*/
int ss_init(void) {
#ifdef _WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0) return 0;
#endif

    return 1;
}


/* Deinitializes the TCP/IP stack. */
void ss_deinit(void) {
#ifdef _WIN32
    WSACleanup();
#endif
}



/* Initializes the struct sockaddr_in pointed to by addr. Sets the
   family to AF_INET, converts the port number and resolves the hostname
   (unless host is NULL, in which case sin_addr is set to INADDR_ANY).
   
   Returns 1 on success, or 0 if the hostname was not found.
*/
int ss_createsockaddr(struct sockaddr_in *addr, const char *host, int port) {
    memset(addr, 0, sizeof(struct sockaddr_in));
    
    addr->sin_family = AF_INET;
    addr->sin_port   = htons((short) port);

    if (host) return ss_lookup(host, &addr->sin_addr);
    else *(u_long *) (&addr->sin_addr) = INADDR_ANY;

    return 1;
}


/* Performs a DNS lookup for the given hostname. If successful,
   saves the IP address into the struct in_addr pointed to by addr.
   
   Returns 1 if successful, otherwise 0.
*/
int ss_lookup(const char *host, struct in_addr *addr) {
    struct hostent *hostEntry;

    if ((hostEntry = gethostbyname(host)) == NULL) {
#ifndef _WIN32
        fprintf(stderr, "Error resolving host %s - %s\n", host, hstrerror(h_errno));
#else
        int err = WSAGetLastError();
        if (err == WSAHOST_NOT_FOUND) fprintf(stderr, "Error resolving host %s - host not found\n", host);
        else fprintf(stderr, "Error resolving host %s - code %d\n", host, err);
#endif
        return 0;
    }
    
    *addr = *(struct in_addr *) hostEntry->h_addr;
    return 1;
}



/* Creates a TCP socket and connects it to the host & port.

   Returns the socket, or INVALID_SOCKET on error.
*/
SOCKET ss_connect(const char *host, int port) {
    struct sockaddr_in socket_addr;
    SOCKET sock;

    // create socket
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
#ifndef _WIN32
        perror("Unable to create socket");
#else
        fprintf(stderr, "Unable to create socket - code %d\n", WSAGetLastError());
#endif
        return INVALID_SOCKET;
    }

    // init the sockaddr_in and resolve the hostname
    if (ss_createsockaddr(&socket_addr, host, port) == 0) {
        closesocket(sock);
        return INVALID_SOCKET;
    }

    // try to connect
    if (connect(sock, (struct sockaddr *) &socket_addr, sizeof(socket_addr)) != 0 ) {
#ifndef _WIN32
        perror(0);
#else
        int err = WSAGetLastError();
        if (err == WSAECONNREFUSED) fprintf(stderr, "Error connecting to %s:%d - connection refused\n", host, port);
        else if (err == WSAETIMEDOUT) fprintf(stderr, "Error connecting to %s:%d - connection timed out\n", host, port);
        else fprintf(stderr, "Error connecting to %s:%d - code %d\n", host, port, err);
#endif
        closesocket(sock);
        return INVALID_SOCKET;
    }

    // success
    return sock;
}



/* Creates a listening TCP socket and binds it to the given port.
   Call ss_accept() to accept incoming connections.

   Returns the socket, or INVALID_SOCKET on error.
*/
SOCKET ss_listen(int port) {
    struct sockaddr_in socket_addr;
    SOCKET sock;

    // create socket
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
#ifndef _WIN32
        perror("Unable to create socket");
#else
        fprintf(stderr, "Unable to create socket - code %d\n", WSAGetLastError());
#endif
        return INVALID_SOCKET;
    }

    ss_createsockaddr(&socket_addr, NULL, port);

    // try to bind
    if (bind(sock, (struct sockaddr *) &socket_addr, sizeof(socket_addr)) != 0 ) {
#ifndef _WIN32
        fprintf(stderr, "Error: cannot bind socket to port %d - ", port);
        perror(0);
#else
        int err = WSAGetLastError();
        if (err == WSAEADDRINUSE) fprintf(stderr, "Error: cannot bind socket to port %d - port is in use\n", port);
        else fprintf(stderr, "Error: cannot bind socket to port %d - code %d\n", port, err);
#endif

        closesocket(sock);
        return INVALID_SOCKET;
    }

    // listen
    if (listen(sock, 5) != 0) {
#ifndef _WIN32
        perror("Error: listen() failed");
#else
        fprintf(stderr, "Error: listen() failed with code %d\n", WSAGetLastError());
#endif

        closesocket(sock);
        return INVALID_SOCKET;
    }

    // success
    return sock;
}



/* Accepts an incoming TCP connection on the socket (created with ss_listen()).
   Fills the address of the connecting entity to the supplied struct
   sockaddr_in (the pointer must not be NULL).

   Returns a new socket associated with the established connection, or
   INVALID_SOCKET on error.
*/
SOCKET ss_accept(SOCKET sock, struct sockaddr_in *peeraddr) {
    int addrsize = peeraddr ? sizeof(struct sockaddr_in) : 0;

    return accept(sock, (struct sockaddr *) peeraddr, &addrsize);
}




/* Sends the given data on the TCP socket - looping until everything is sent.

   Returns 1 on success, 0 on failure (connection broken).
*/
int ss_send(SOCKET sock, const void *message, int msglen) {
    int ret, sent = 0;

    // send the message
    do {
        ret = send(sock, ((const char *) message) + sent, msglen-sent, 0);
        sent += ret;
    } while (sent < msglen && ret != -1);

    if (ret == -1) return 0;

    return 1;
}


/* Receives data from the TCP socket into a buffer, up to a maximum of
   maxlen bytes.
   
   Returns the number of bytes received, or -1 on error (0 when the peer
   has shut down the connection).
*/
int ss_receive(SOCKET sock, void *buffer, int maxlen) {
    return recv(sock, buffer, maxlen, 0);
}



/* Creates a UDP socket and binds it to the given port.

   Returns the socket, or INVALID_SOCKET on error.
*/
SOCKET ss_udpsocket(int port) {
    struct sockaddr_in socket_addr;
    SOCKET sock;

    // create socket
    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == INVALID_SOCKET) {
#ifndef _WIN32
        perror("Unable to create socket");
#else
        fprintf(stderr, "Unable to create socket - code %d\n", WSAGetLastError());
#endif
        return INVALID_SOCKET;
    }

    // init the sockaddr_in
    ss_createsockaddr(&socket_addr, NULL, port);

    // try to bind
    if (bind(sock, (struct sockaddr *) &socket_addr, sizeof(socket_addr)) != 0 ) {
#ifndef _WIN32
        fprintf(stderr, "Error: cannot bind socket to port %d - ", port);
        perror(0);
#else
        int err = WSAGetLastError();
        if (err == WSAEADDRINUSE) fprintf(stderr, "Error: cannot bind socket to port %d - port is in use\n", port);
        else fprintf(stderr, "Error: cannot bind socket to port %d - code %d\n", port, err);
#endif

        closesocket(sock);
        return INVALID_SOCKET;
    }

    // success
    return sock;
}


/* Sends a UDP datagram from the socket to the given destination.

   Returns the number of bytes sent, or -1 on error.
*/
int ss_udpsend(SOCKET sock, const struct sockaddr_in *dest, const void *message, int msglen) {
    return sendto(sock, message, msglen, 0, (const struct sockaddr *) dest, sizeof(struct sockaddr_in));
}



/* Receives a UDP datagram from the socket into the buffer, and fills
   the sender's address to the struct sockaddr_in (the pointer may be NULL).
   If the buffer isn't large enough for the datagram, the excess bytes may be
   discarded.

   Returns the number of bytes received, or -1 on error.
*/
int ss_udpreceive(SOCKET sock, struct sockaddr_in *from, void *buffer, int maxlen) {
    int len = from ? sizeof(struct sockaddr_in) : 0;
    return recvfrom(sock, buffer, maxlen, 0, (struct sockaddr *) from, &len);
}





